package scoutobahn.highway.factory;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;

import scoutobahn.highway.DistanceCalculator;
import scoutobahn.highway.Highway;
import scoutobahn.highway.HighwayElement;
import scoutobahn.highway.HighwayGraph;
import scoutobahn.highway.HighwayNode;
import scoutobahn.highway.Town;
import scoutobahn.xml.OsmNode;
import scoutobahn.xml.OsmRelation;
/**
 * Factory for building a HighwayGraph.
 * @author mark
 *
 */
public class HighwayGraphFactory {
    private Map<OsmRelation, Collection<OsmNode>> nodes;
    private Map<OsmNode, HighwayNode> highwayNodeCache;
    private Collection<OsmNode> towns;
    private DistanceCalculator calculator;
    private long townThreshold;
    /**
     * Constructs a new HighwayGraphFactory.
     */
    public HighwayGraphFactory() {
        this.highwayNodeCache = new HashMap<OsmNode, HighwayNode>();
    }
    /**
     * Set's the osm data for the Highway.
     * @param osmData The osm data
     */
    public void setHighways(final Map<OsmRelation,
            Collection<OsmNode>> osmData) {
        this.nodes = osmData;
    }
    /**
     * Set's the towns.
     * @param townNodes The towns
     */
    public void setTowns(final Collection<OsmNode> townNodes) {
        this.towns = townNodes;
    }
    /**
     * Sets the DistanceCalculator.
     * @param calc The DistanceCalculator to use
     */
    public void setDistanceCalculator(
            final DistanceCalculator calc) {
        this.calculator = calc;
    }
    /**
     * Sets the max distance between a Town and a Highway.
     * @param dist The max distance in meters
     */
    public void setTownHighwayDistance(final long dist) {
        this.townThreshold = dist;
    }
    /**
     * Builds the HighwayGraoh from the given data.
     * @return The constructed HighwayGraph
     */
    public HighwayGraph buildGraph() {
        LinkedList<Highway> ways = new LinkedList<Highway>();
        for (Entry<OsmRelation, Collection<OsmNode>> def
                : this.nodes.entrySet()) {
            HashSet<HighwayNode> highwayNodes = new HashSet<HighwayNode>();
            Highway way = new Highway(def.getKey(), this.calculator);
            for (OsmNode node : def.getValue()) {
                highwayNodes.add(this.toHighwayNode(way, node));
            }
            way.setNodes(highwayNodes);
            ways.add(way);
        }
        LinkedList<Town> graphTowns = new LinkedList<Town>();
        for (OsmNode node : this.towns) {
            graphTowns.add(this.toTown(node, ways));
        }
        return new HighwayGraph(ways, graphTowns);
    }
    private HighwayNode toHighwayNode(final HighwayElement from,
            final OsmNode node) {
        if (!this.highwayNodeCache.containsKey(node)) {
            Collection<HighwayElement> belongsTo =
                    new LinkedList<HighwayElement>();
            this.highwayNodeCache.put(node, new HighwayNode(belongsTo, node));
        }
        this.highwayNodeCache.get(node).getParents().add(from);
        return this.highwayNodeCache.get(node);
    }
    private Town toTown(final OsmNode townNode,
            final Collection<Highway> ways) {
        Town newTown = new Town(townNode);
        for (Highway way : ways) {
            for (HighwayNode node : way.getHighwayNodes()) {
                if (this.calculator.getDistance(townNode, node.getOsmItem())
                        < this.townThreshold) {
                    newTown.getParents().add(node);
                    node.getParents().add(newTown);
                }
            }
        }
        return newTown;
    }
}
